Skip to content

Batch 34b: preview-gated one-click axes, header evidence on reopen, NaN crop - #255

Merged
guijacquemet merged 5 commits into
mainfrom
claude/batch34b-fixes
Nov 9, 2026
Merged

guijacquemet merged 5 commits into
mainfrom
claude/batch34b-fixes

Conversation

@guijacquemet

@guijacquemet guijacquemet commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

External audit batch 34b, checked against main at bf733ee. I did not touch the image-type gate or block confirm (draft_answers.py, notebook_channels_grid.py, channel_units.py), and I did not touch the P21/P29 preflight wiring.

Each regression test below fails on bf733ee and passes on this branch. To confirm that, I reverted only src/ and ran the 7 new tests: all 7 failed, each on its intended assertion.

BLOCKER 3: one-click axes could confirm a preview that was never shown. STILL HOLDS

Reproduction: the header is readable but read_plane raises PlaneReadError. The preview is cleared, but quick_confirm stays enabled. _confirm_quick_axes() then returns True and sets preview_checked=True.

Fix:

  • _render_preview records _preview_drawn: the mapping of the plane now shown, or None when no plane is shown (no image fits, or the read failed).
  • The quick card enables Confirm these axes only when a PNG is shown and was drawn with exactly the offered roles. Otherwise it shows the read failure plus "Confirm these axes is not offered: no preview of these roles is shown."
  • _confirm_quick_axes refuses the click, and never sets preview_checked, when the preview is empty, failed or stale.

Tests (tests/test_notebook_axes_quick.py):

  • test_a_preview_that_cannot_be_read_is_not_confirmed_by_one_click
  • test_a_preview_that_fails_later_withdraws_the_click

BLOCKER 4: an unchanged-folder fold could hide a changed-header refusal. STILL HOLDS

Reproduction (real workflow): publish DNA@C0 / ACTIN@C1, then rewrite ctrl/rep1/a.tif with the header names swapped, keeping the same file, shape and count. On reopen:

  • PublishedState.changed is False.
  • No unit has a reason.
  • The screen folds under "Saved on …, nothing changed".
  • The run's checks still give R_HEADER_CONTRADICTION.

Fix (root cause):

  • observe_folders(..., metadata=) builds each unit's header evidence from the headers the preflight already read: the channel count and raw header names (physical_check.layout_of, without roles). A count the header leaves open stays unknown. A unit whose headers disagree with each other is marked ObservedUnit.uniform=False, which counts as channels_changed.
  • published_state (notebook path) passes pre.metadata.files.
  • PublishedState.changed now also covers rec.reopened, so every reason that reopens a repeat counts as a change.
  • The unit is named in orange ("headers changed: ctrl/rep1 …"), and the step-1 chip counts folders with changed headers.

Second layer: when the run's checks have run on the published file (roles confirmed) and refuse it:

  • the screen unfolds;
  • "nothing changed" and "The run (step 12) can start" are not shown.

This also catches a refusal the reconciliation cannot see, such as a mask type added beside unchanged folders.

Tests:

  • tests/test_notebook_row_fold.py::test_a_header_rewritten_under_the_same_files_keeps_its_row_open: real reopen, no injected reasons. The row stays open, R_HEADER_CONTRADICTION is visible, and nothing says the run can start.
  • tests/test_notebook_row_fold.py::test_a_refusal_of_the_run_s_checks_unfolds_an_unchanged_reopen
  • tests/test_reconcile_observe.py::test_the_headers_read_give_each_unit_its_channel_evidence

FILE 3: saved axis roles were offered after a header-only change. STILL HOLDS

Reproduction: after a run, a header replacement with the same image count still offered "the roles the earlier run of this experiment recorded".

Fix: with BLOCKER 4's evidence, state.changed is True, so _saved_axes() returns None. The card offers only the roles the headers name now, with "Some image headers changed since the experiment was saved … Review these afresh." Nothing is confirmed without the click.

Test: tests/test_notebook_axes_quick.py::test_a_header_change_does_not_offer_the_earlier_run_s_roles

FILE 1: there was no NaN-containing crop test. STILL HOLDS, and it found real defects

Defects found:

  • Magenta non-finite pixels counted as "detail", so a NaN block outside the busy square took the crop: (0,0) instead of (256,640).
  • A non-finite pixel only in the native crop was not named anywhere.
  • The thumbnail note said "N non-finite pixels shown in magenta" twice.

Fix:

  • busiest_square/_where measure finite thumbnail pixels only.
  • crop_text names the crop's own non-finite pixels.
  • display_panel names them once.
  • A colour crop with non-finite values keeps its thumbnail and says why there is no detail.

Source arrays are never changed.

Test: tests/test_notebook_thumbnails.py::test_non_finite_pixels_inside_and_outside_the_crop_are_flagged_and_do_not_move_it

Coordinator item: bind the axis preview to the checked header. Fixed (independent of #254)

The axis-step preview read planes as the file is now, not as the folder check read its header.

Fix:

  • The notebook folder check now calls preflight(..., bind_headers=True). The line and comment are identical to Audit batches 33, 34a, 34b: publish only what was answered, of the image checked #254's, so the two branches merge cleanly; I checked by test-merging origin/claude/batch33-fixes, and the notebook, reconcile and batch-33 tests then gave 420 passed.
  • _render_preview reads a plane only while the file has the fingerprint the check took with its header, checked before and after the read.
  • An image that is unbound or changed gives "… changed since the folder check read it: check the folder again." That counts as a failed preview: no image is shown, Confirm these axes is neither offered nor accepted, and preview_checked stays False.
  • This branch does not merge claude/batch33-fixes and does not use its new helpers (check_unchanged/checked_fingerprint). The check is done locally with Fingerprint.of.
  • tests/test_notebook_masks.py gets the same bind_headers=True one-line change as Audit batches 33, 34a, 34b: publish only what was answered, of the image checked #254.

Test: tests/test_notebook_axes_quick.py::test_an_image_changed_since_the_folder_check_is_not_previewed_or_confirmed

Gate

  • python tools/check.py:
    • ruff check: clean
    • ruff format: 409 files already formatted
    • pytest: 5022 passed, 4 skipped (case-insensitive file system ×3, root file modes ×1)
    • code rules: pass, baseline not grown
  • tests/test_spec_*: 1649 passed, 0 skipped

https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc


Generated by Claude Code

…aN crop

- BLOCKER 3: Confirm these axes is offered (and accepted) only while a
  preview drawn with exactly the offered roles is shown; a failed, empty
  or stale preview disables the button, keeps the failure visible and
  never sets preview_checked.
- BLOCKER 4: the reopen reconciles with the preflight's header evidence
  (channel count, raw header names per unit; a unit whose headers
  disagree is not uniform), so a header rewritten under the same files is
  channels_changed: the row stays open, the unit is named, and
  PublishedState.changed covers every reopening reason. Second layer: a
  refusal of the run's checks on the published file unfolds the screen
  and drops "nothing changed" / "can start".
- FILE 3: with that evidence, a header-only change no longer offers the
  earlier run's recorded roles; the card says the headers changed.
- FILE 1: non-finite pixels no longer count as crop detail, the crop's
  own non-finite pixels are named, a colour crop with non-finite values
  keeps its thumbnail, and the non-finite note is no longer doubled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
The axis-step preview read each plane as the file is now. The folder
check now binds every header to its file (bind_headers=True, as batch 33
does), and the preview reads a plane only while the file still has the
fingerprint the check took with its header, before and after the read.
A changed or unbound image is a failed preview: "check the folder
again", no image, and the one-click confirmation is neither offered nor
accepted (preview_checked stays False).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
…l change

An RGB unit rewritten as a plain three-channel image (same files, image
count and channel count) reconciled as unchanged and folded under
"nothing changed", while the run's checks refuse it with
IMAGE_TYPE_CONTRADICTS_HEADER. ObservedUnit now carries the headers' RGB
flag (None for OME-Zarr, as the run's checks skip it) and a flag that
contradicts the declared image type counts as channels_changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
The axis preview compared Fingerprint.of itself; it now uses
preview.checked_fingerprint and preview.check_unchanged, as the channel
thumbnails, header previews and mask evidence do, before and after the
read. A PreviewRefused is a failed preview. Adds a regression test for a
file rewritten during the read (fails without the after-read check).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
@guijacquemet
guijacquemet merged commit 4e8e982 into main Nov 9, 2026
7 checks passed
@guijacquemet
guijacquemet deleted the claude/batch34b-fixes branch November 9, 2026 17:45
guijacquemet added a commit that referenced this pull request Nov 9, 2026
guijacquemet pushed a commit that referenced this pull request Nov 10, 2026
Brings the audit batch 33/34a/34b fixes and the pre-release follow-ups into the v0.5.0
release candidate, keeping the P21/P29 description_in_use wiring.

Conflicts:
- notebook_folder.py: the folder check passes both with_description (P21/P29) and
  bind_headers=True (audit batch 33).
- cli_describe.py: the module docstring says both that describe reads image headers when it
  compares a published description and that a non-NFC folder label is refused; the imports
  of both sides are kept.
- CHANGELOG.md: main's Unreleased entries are folded into the 0.5.0 section, after the
  P21/P29 fix; the 0.5.0 introduction names #254, #255 and #257.

Also: the P30 test's answers (added on this branch) now answer the image type as
"channels", since an unanswered image type is an open question since audit batch 33
(unit_image_type_unanswered). tools/check.py: ruff clean, 415 files formatted,
5105 passed, 4 skipped (environment), code rules passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet pushed a commit that referenced this pull request Nov 10, 2026
RELEASE_REF (and the test asserting it) now names 0ea3254, the merge of origin/main
(#254, #255, #257) into claude/release-0.5.0, which carries the P21/P29 fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
guijacquemet pushed a commit that referenced this pull request Nov 10, 2026
Only docs/conformance.md: the measured commit (ba415de, whose RELEASE_REF is the merge
0ea3254), the counts (5109 collected, 5105 passed, 4 skipped; Spec modules 1671 passed,
0 skipped), tools/check.py (415 files, 5105 passed), the P30 answers now stating the image
type, and the fixes merged from main: audit batches 33/34a (#254), 34b (#255) and the
pre-release follow-ups (#257).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G3qp9AxjpGPw9xfWAUJNqc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants